﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

namespace Licenta1.RayTracer
{
    public class Ray
    {
        public Vector point;
        public Vector direction;
    }

    public delegate Color PixelRenderer(int x, int y);

    public class RayTracer
    {
        Vector camera;
        double top, bottom, left, right;
        int width, height;
        const int MaxDepth = 10;

        List<RTObject> objects;
        List<PointLight> pointLights;

        public RayTracer(int Width, int Height) : this(new Vector(0, 0, 50), 50, -50, -50, 50, Width, Height) { }

        public RayTracer(Vector cam, double Top, double Bottom, double Left, double Right, int Width, int Height)
        {
            camera = cam;
            top = Top;
            bottom = Bottom;
            left = Left;
            right = Right;
            width = Width;
            height = Height;

            objects = new List<RTObject>();
            pointLights = new List<PointLight>();

            //objects.Add(new RTObject(new CSG(new CSG(
            //    new MathSphere(new Vector(10, 0, -105), 20),
            //    new MathSphere(new Vector(-10, 0, -100), 20), CSG.Operator.Union),
            //    new MathSphere(new Vector(0, 0, -90), 20),
            //    CSG.Operator.Intersection), new SolidColorMaterial(0, 1, 0)));


            //objects.Add(new RTObject(new CSG(
            //    new MathCube(new Vector(20, 0, -70), 20),
            //    new MathSphere(new Vector(20, 0, -70), 12),
            //    CSG.Operator.Difference),
            //    new SolidColorMaterial(1, 0, 0, 0.7)));

            /*
            for (int i = 0; i < 10; i++)
            {
                double x = 20 * Math.Sin((i / 10.0) * 2 * Math.PI);
                double y = 15;
                double z = -50 + 20 * Math.Cos((i / 10.0) * (2 * Math.PI));

                objects.Add(new RTObject(new MathSphere(new Vector(x, y, z), 5), new SolidColorMaterial(1, 0, 0)));
            }*/
            //objects.Add(new RTObject(new MathSphere(new Vector(10, 0, -110), 20), new SolidColorMaterial(1,0,0)));
            //objects.Add(new RTObject(new MathSphere(new Vector(-10, 0, -90), 20), new SolidColorMaterial(0, 1, 0)));
            //objects.Add(new RTObject(new MathPlane(0, 1, 0, 10), new SolidColorMaterial(0, 0, 1, 0.1)));
            //objects.Add(new RTObject(new MathPlane(0, 0, 1, 10), new SolidColorMaterial(0, 0, 0.5)));
            //objects.Add(new SolidObject(new MathPlane(1, 0, 0, 10), Color.HotPink));

            //pointLights.Add(new PointLight(new Vector(-10, 50, -50), new Colors(0.5, 0.5, 0.5), 100));
            //pointLights.Add(new PointLight(new Vector(10, 150, -50), new Colors(0.5, 0.5, 0.5), 100));
        }

        private Colors GetRayColor(Ray ray, int depth)
        {
            List<Intersection> intersections = new List<Intersection>();
            foreach (RTObject obj in objects)
            {
                double[] distances = obj.Intersects(ray);
                foreach (double d in distances)
                    intersections.Add(new Intersection(d, obj));
            }


            intersections.Sort();

            if (intersections.Count == 0)
                return Colors.Black;

            RTObject rTobj = intersections[0].obj;
            Vector point = ray.point + ray.direction * intersections[0].distance;


            Colors c = ((SolidColorMaterial)rTobj.MATERIAL).COLORS;

            Colors ambient = c.Multiply(new Colors(1, 1, 1).Intensify(0.6));
            Colors finalLight = ambient;

            foreach (PointLight light in pointLights)
            {
                bool isInShadow = false;
                Ray shadowRay = new Ray();
                shadowRay.point = point;
                shadowRay.direction = (light.POINT - point).Normalize();

                foreach (RTObject obj in objects)
                {
                    double[] z = obj.Intersects(shadowRay);
                    if (z.Length > 0)
                    {
                        Array.Sort(z);
                        if (z.Last() > 10e-10)
                        {
                            isInShadow = true;
                            break;
                        }
                    }
                }

                if (isInShadow)
                {
                    continue;
                }

                double angle = Vector.Angle(shadowRay.direction, rTobj.SHAPE.GetNormal(point));

                double intensity = 0;

                if (angle < (Math.PI / 2) && angle >= 0)
                    intensity = 1.0 - (angle / (Math.PI / 2.0));

                Colors lightColor = light.COLORS.Intensify(intensity);

                finalLight = finalLight + lightColor;
            }

            if ( depth > MaxDepth)
                return finalLight;

            Ray reflectionRay = new Ray();
            reflectionRay.point = ray.point + ray.direction * (intersections[0].distance - 10e-10);

            reflectionRay.direction = ray.direction + ( rTobj.SHAPE.GetNormal(point) * 2.0 * (- (rTobj.SHAPE.GetNormal(point) * ray.direction)));
            
            return finalLight.Intensify(1 - rTobj.MATERIAL.GetReflectivity()) + GetRayColor(reflectionRay, 
                depth+1).Intensify(rTobj.MATERIAL.GetReflectivity());

        }

        public Color GetPixel(int x, int y)
        {
            double X = left + x * ((right - left) / width);

            double Y = top - y * ((top - bottom) / height);

            Ray ray = new Ray();
            ray.point = camera;
            ray.direction = (new Vector(X, Y, 0) - camera).Normalize();

            return GetRayColor(ray, 0).getColor();
            
        }

        bool IsShadowed(Vector point, PointLight light)
        {
            double dist = (light.POINT - point).Length();
            Ray ray = new Ray();

            ray.point = point;
            ray.direction = (light.POINT - point).Normalize();

            foreach (RTObject obj in objects)
            {
                double[] distances = obj.Intersects(ray);

                foreach (double d in distances)
                    if (d > 0.001 && d < dist)
                        return true;
            }

            return false;
        }

        public void SetCamera(Vector newCamera)
        {
            camera = newCamera;
        }

        public void AddLight(Vector position, Colors color, double fade_distance)
        {
            pointLights.Add(new PointLight(position, color, fade_distance));
        }

        public void AddObject(MathShape shape, Material material)
        {
            objects.Add(new RTObject(shape, material));
        }

        public void AddObject(MathShape shape)
        {
            objects.Add(new RTObject(shape));
        }
    }
    class Intersection : IComparable<Intersection>
    {
        public double distance;
        public RTObject obj;

        public Intersection(double d, RTObject m)
        {
            distance = d;
            obj = m;
        }
        
        public int CompareTo(Intersection other)
        {
            if (this.distance < other.distance)
                return -1;
            else if (this.distance == other.distance)
                return 0;
            else 
                return 1;
        }
    }



    class Utils
    {
        public static StreamWriter fs;

        public static void Write(string line)
        {
            fs = new StreamWriter("console_out.txt", true);

            fs.WriteLine(line);

            fs.Close();
            return;
        }
    }
}
